
#ifndef _TTG_SIMULATION_SIMPLE_
#define _TTG_SIMULATION_SIMPLE_

#include <ttg/structures.h>
#include <ttg/services.h>
#include <ttg/pipeline.h>

#include "Common/ICalculationCore.h"

using namespace ttg::structures;
using namespace ttg::pipeline;
using namespace ttg::utils;

#include "cudaBindings.h"

class ttgSplitter :public Node<InterleavedBodies::SubVector, InterleavedBodies::SubVector>
{
	public:
		virtual void process(InterleavedBodies::SubVector data)
		{
			ParameterDouble &ratio = getContext().getVariable<ParameterDouble>("ratio");
			size_t gpu_part = (size_t)(data.getSize() * ratio);
			size_t cpu_part = (size_t)(data.getSize() - gpu_part);
			//Cutting part for GPU.
			if (gpu_part != 0)
			{
				InterleavedBodies::SubVector part(data, 0, gpu_part);
				part.setTag(42);				//GPU-binding will process only parts with "42" tags.
				sendNext(part);
			}
			//Cutting CPU parts.
			size_t part_count = ttg::services::CPUAnalyzer::getRef()->getCoreCount() * 4;
			for (size_t i = 0; i < part_count; i++)
			{
				size_t lo = (i * cpu_part) / part_count;
				size_t hi = ((i + 1) * cpu_part) / part_count;
				if (lo != hi)
					sendNext(InterleavedBodies::SubVector(data, gpu_part + lo, gpu_part + hi));
			}
		}
		inline ttgSplitter()
			:Node("ttgSplitter")
		{ /*nothing*/ }
};

class ttgGatherer :public Node<InterleavedBodies::SubVector, InterleavedBodies::SubVector>
{
	private:
		InterleavedBodies::SubVector res;
	public:
		virtual void process(InterleavedBodies::SubVector data)
		{ res = data; }
		virtual void onEnd()
		{ sendNext(res.getOwnerSubVector()); }
		inline ttgGatherer()
			:Node("ttgGatherer")
		{ /*nothing*/ }
};

class ttgInteractor :public Node<InterleavedBodies::SubVector, InterleavedBodies::SubVector>
{
	public:
		virtual void process(InterleavedBodies::SubVector data)
		{
			InterleavedBodies::SubVector owner = data.getOwnerSubVector();
			for (size_t i = 0; i < data.getSize(); i++)
				for (size_t j = 0; j < owner.getSize(); j++)
					data.interactWith(i, owner, j);
			sendNext(data);
		}
		inline ttgInteractor()
			:Node("ttgInteractor")
		{ addBinding(new cudaInteractor()); }
};

class ttgUpdater :public Node<InterleavedBodies::SubVector, InterleavedBodies::SubVector>
{
	public:
		virtual void process(InterleavedBodies::SubVector data)
		{
			float dt = getContext().getVariable<float>("dt");				//this variable should be defined
																			//in the user context.
			for (size_t i = 0; i < data.getSize(); i++)
				data.update(i, dt);
			sendNext(data);
		}
		inline ttgUpdater()
			:Node("ttgUpdater")
		{ /*nothing*/ }
};

//Version, that uses simple pipeline with CUDA bindings.
class ttgCore_Simple :public ICalculationCore
{
	private:
		ParameterSet *ttglib_simple_filter;
		ParameterDouble *ratio;
		Pipeline<InterleavedBodies::SubVector, InterleavedBodies::SubVector> *pl;
	public:

		//Setting parameters and creating pipeline.
		inline ttgCore_Simple()
		{
			ttglib_simple_filter = new ParameterSet("ttgLib(Simple) settings");
			ratio = new ParameterDouble("CPU <-> GPU ratio", ttglib_simple_filter, 0.0, 1.0);
			*ratio = ttg::devices::GPU::CUDA::getRef()->isAvailable() ? 0.9 : 0.0;		//to avoid bad splitting
			pl = new Pipeline<InterleavedBodies::SubVector, InterleavedBodies::SubVector>(
							wrap(new ttgSplitter()) + wrap(new ttgInteractor()) + wrap(new ttgGatherer()) +
							wrap(new ttgSplitter()) + wrap(new ttgUpdater()),
							"ttgLib Simple Pipeline");
		}

		virtual double simulate(InterleavedBodies::SubVector bodies, float dt)
		{
			//Setting correct context for pipeline.
			UserContext context;
			context.registerVariable("dt", &dt);
			context.registerVariable("ratio", ratio);
			pl->setUserContext(context);
			//Launching.
			double t_start = ttg::services::Timer::getRef()->getSeconds();
			pl->run(bodies);
			double t_end = ttg::services::Timer::getRef()->getSeconds();
			//Returning elapsed time.
			return t_end - t_start;
		}

		virtual ~ttgCore_Simple()
		{
			SAFE_DELETE(pl);
			SAFE_DELETE(ratio);
			SAFE_DELETE(ttglib_simple_filter);
		}
};

#endif
